﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace PasToCsvConverter
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (convertBackgroundWorker.IsBusy)
            {
                e.Cancel = true;
            }
        }

        private void convertBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            object[] args = (object[])e.Argument;

            string[] files = (string[])args[0];
            bool removeEmptyCols = args.Length > 1 && (bool)args[1];
            
            bool excel2003Flag = false;
            int count = 0; // numero de conversões feitas com sucesso
            int successCount = 0;

            Exception exception = null;

            TimeSpan avg = TimeSpan.Zero;

            foreach (string file in files)
            {
                if (e.Cancel)
                    break;

                DateTime start = DateTime.Now;

                FileInfo fi = new FileInfo(file);
                if (fi.Exists == false)
                    exception = new FileNotFoundException("O arquivo a ser convertido não existe", file);

                //bool result = ConvertAndExport(file, out successCount, out exception);
                bool result = ConvertAndExportFast(file, removeEmptyCols, ref excel2003Flag, out exception);

                if (result)
                {
                    successCount++;
                }

                TimeSpan elapsed = DateTime.Now - start;

                if (e.Cancel)
                    break;

                if (result == false && exception != null)
                {
                    throw exception;
                }

                if (avg != TimeSpan.Zero)
                {
                    avg = TimeSpan.FromMilliseconds((avg.TotalMilliseconds + elapsed.TotalMilliseconds) / 2d);
                }
                else
                {
                    avg = elapsed;
                }

                count++;
                convertBackgroundWorker.ReportProgress((int)Math.Round(count * 100d / files.Length, 0));
            }

            if (e.Cancel)
                return;

            System.Diagnostics.Debug.WriteLine("avg time: " + avg.ToString());

            convertBackgroundWorker.ReportProgress(100);
            
            System.Threading.Thread.Sleep(1500);

            if (successCount > 0)
                e.Result = new object[] { successCount, excel2003Flag };
            else if (exception != null)
                throw exception;
            else
                throw new Exception("ocorreu um erro insperado ao converter os dados");
        }

        [Obsolete]
        private bool ConvertAndExport(string file, out Exception exception)
        {
            DateTime start = DateTime.Now;

            DataTable data = null;

            bool result = Convert(file, out data, out exception);
            if (result)
            {
                string destination = file + ".csv";

                result = Export(data, destination, out exception);
            }

            TimeSpan elapsed = DateTime.Now - start;
            System.Diagnostics.Debug.WriteLine("elapsed time: " + elapsed.ToString());

            return result;
        }

        [Obsolete]
        private bool Convert(string file, out DataTable data, out Exception exception)
        {
            exception = null;
            data = null;

            try
            {
                DateTime start = DateTime.Now;

                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    using (TextReader tw = new StreamReader(fs))
                    {
                        #region ler data (primeira linha)

                        string line = tw.ReadLine();

                        DateTime date = DateTime.MinValue;

                        string dateTimeStr = line.Replace("Sab", "Sáb");

                        const System.Globalization.DateTimeStyles opts =
                            System.Globalization.DateTimeStyles.AssumeLocal |
                            System.Globalization.DateTimeStyles.AllowWhiteSpaces;

                        System.Globalization.CultureInfo[] cultures = new System.Globalization.CultureInfo[]
                        {
                            System.Globalization.CultureInfo.CurrentCulture,
                            System.Globalization.CultureInfo.CurrentUICulture,
                            System.Globalization.CultureInfo.InvariantCulture,
                            System.Globalization.CultureInfo.CreateSpecificCulture("PT-BR"),
                        };

                        foreach (System.Globalization.CultureInfo provider in cultures)
                        {
                            // parse example: "Sab jul 07 00:00:01 2012"

                            if (DateTime.TryParseExact(dateTimeStr, "ddd MMM dd HH:mm:ss yyyy", provider.DateTimeFormat, opts, out date))
                            {
                                break;
                            }
                        }

                        if (date == DateTime.MinValue)
                        {
                            throw new Exception("Não há informação de data no arquivo");
                        }

                        string dateStr = date.ToString("dd/MM/yyyy") + " ";

                        line = tw.ReadLine();
                        if (line == null)
                            throw new Exception("Não há dados a serem lidos no arquivo");

                        #endregion

                        #region ler informações das colunas (segunda linha)

                        int colCount = 0;
                        int colLength = 0;

                        string[] parts = line.Split(new char[] { ' ' }, 3, StringSplitOptions.None);
                        if (parts.Length >= 2)
                        {
                            if (parts[0].StartsWith("c"))
                            {
                                int.TryParse(parts[1], out colCount);
                                int.TryParse(parts[2], out colLength);
                            }
                        }

                        if (colCount <= 0)
                            throw new Exception("Não há informação das colunas no arquivo");

                        #endregion

                        #region ler os nomes das colunas

                        data = new DataTable();

                        data.Columns.Add("Data e Hora", typeof(string));

                        for (int k = 0; k < colCount; k++)
                        {
                            line = tw.ReadLine();
                            if (line == null)
                                break;

                            string name = line.Trim();
                            data.Columns.Add(name, typeof(string));
                        }

                        #endregion

                        #region ler amostras

                        line = tw.ReadLine();

                        while (line != null)
                        {
                            // obter hora da amostra
                            string[] lparts = line.Split(new char[] { ' ' }, 2, StringSplitOptions.None);
                            if (lparts[0].StartsWith("h") == false)
                                throw new Exception("Erro ao obter hora da amostra");

                            DateTime time;
                            if (DateTime.TryParse(lparts[1], out time) == false)
                                throw new Exception("Erro ao obter hora da amostra");

                            DataRow row = data.Rows.Add();

                            row[0] = dateStr + time.ToString("HH:mm:ss");

                            line = tw.ReadLine();

                            for (int c = 0; c < colCount; c++)
                            {
                                if (line == null)
                                    break;

                                if (line.Last() != '*')
                                {
                                    row[c + 1] = line.Trim();
                                }

                                line = tw.ReadLine();
                            }
                        }

                        #endregion
                    }
                }

                TimeSpan elapsed = DateTime.Now - start;

                System.Diagnostics.Debug.WriteLine("convert time: " + elapsed);

                return true;

            }
            catch (Exception e)
            {
                exception = e;
                data = null;
                return false;
            }
        }

        [Obsolete]
        private bool Export(DataTable data, string destination, out Exception exception)
        {
            exception  = null;

            if (data == null || string.IsNullOrEmpty(destination))
                return false;
            if (data.Columns.Count <= 1 || data.Rows.Count <= 0)
                return false;

            char separator = ';';

            try
            {
                DateTime start = DateTime.Now;

                if (File.Exists(destination))
                    File.Delete(destination);

                using (FileStream fs = new FileStream(destination, FileMode.Create, FileAccess.Write))
                {
                    using (TextWriter tw = new StreamWriter(fs))
                    {
                        #region export headers

                        if (data.Columns.Count > 0)
                        {
                            StringBuilder sb = new StringBuilder();

                            foreach (DataColumn col in data.Columns)
                            {
                                sb.Append(col.ColumnName);
                                sb.Append(separator);
                            }

                            string line = sb.ToString().TrimEnd(separator);

                            tw.WriteLine(line);
                        }

                        #endregion

                        #region export rows

                        foreach (DataRow row in data.Rows)
                        {
                            StringBuilder sb = new StringBuilder();
                            for (int k = 0; k < data.Columns.Count; k++)
                            {
                                if (row[k] != DBNull.Value)
                                {
                                    sb.Append(row[k].ToString());
                                }
                                sb.Append(separator);
                            }

                            string line = sb.ToString().TrimEnd(separator);

                            tw.WriteLine(line);
                        }

                        #endregion
                    }
                }

                TimeSpan elapsed = DateTime.Now - start;

                System.Diagnostics.Debug.WriteLine("export time: " + elapsed);

                return true;
            }
            catch (Exception e)
            {
                exception = e;
                return false;
            }
        }

        private bool ConvertAndExportFast(string file, bool removeEmptyCols, ref bool excel2003Flag, out Exception exception)
        {
            exception = null;

            if (string.IsNullOrEmpty(file))
                return false;

            char separator = ';';

            try
            {
                DateTime start = DateTime.Now;

                string destination = file + ".csv";

                if (File.Exists(destination))
                    File.Delete(destination);

                string dateStr = null;
                int colCount = 0;
                int rowCount = 0;

                bool[] emptyColsMap = null;                

                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    using (TextReader tr = new StreamReader(fs))
                    {
                        #region ler data (primeira linha)

                        string line = tr.ReadLine();

                        DateTime date = DateTime.MinValue;

                        string dateTimeStr = line.Replace("Sab", "Sáb");

                        const System.Globalization.DateTimeStyles opts =
                            System.Globalization.DateTimeStyles.AssumeLocal |
                            System.Globalization.DateTimeStyles.AllowWhiteSpaces;

                        System.Globalization.CultureInfo[] cultures = new System.Globalization.CultureInfo[]
                        {
                            System.Globalization.CultureInfo.CurrentCulture,
                            System.Globalization.CultureInfo.CurrentUICulture,
                            System.Globalization.CultureInfo.InvariantCulture,
                            System.Globalization.CultureInfo.CreateSpecificCulture("PT-BR"),
                        };

                        foreach (System.Globalization.CultureInfo provider in cultures)
                        {
                            // parse example: "Sab jul 07 00:00:01 2012"

                            if (DateTime.TryParseExact(dateTimeStr, "ddd MMM dd HH:mm:ss yyyy", provider.DateTimeFormat, opts, out date))
                            {
                                break;
                            }
                        }

                        if (date == DateTime.MinValue)
                        {
                            throw new Exception("Não há informação de data no arquivo");
                        }

                        dateStr = date.ToString("dd/MM/yyyy") + " ";

                        line = tr.ReadLine();
                        if (line == null)
                            throw new Exception("Não há dados a serem lidos no arquivo");

                        #endregion

                        #region ler informações das colunas (segunda linha)

                        int colLength = 0;

                        string[] parts = line.Split(new char[] { ' ' }, 3, StringSplitOptions.None);
                        if (parts.Length >= 2)
                        {
                            if (parts[0].StartsWith("c"))
                            {
                                int.TryParse(parts[1], out colCount);
                                int.TryParse(parts[2], out colLength);
                            }
                        }

                        if (colCount <= 0)
                            throw new Exception("Não há informação das colunas no arquivo");

                        #endregion

                        #region pular os nomes das colunas

                        for (int k = 0; k < colCount; k++)
                        {
                            line = tr.ReadLine();
                            if (line == null)
                                throw new Exception("Não há informação das colunas no arquivo");
                        }

                        #endregion

                        #region procurar amostras nulas

                        emptyColsMap = new bool[colCount];

                        if (removeEmptyCols)
                        {
                            for (int k = 0; k < colCount; k++)
                            {
                                emptyColsMap[k] = true;
                            };


                            line = tr.ReadLine();

                            while (line != null)
                            {
                                // obter hora da amostra
                                if (line.StartsWith("h", StringComparison.OrdinalIgnoreCase) == false)
                                    break;

                                line = tr.ReadLine();

                                for (int k = 0; k < colCount; k++)
                                {
                                    if (line == null)
                                        break;

                                    bool isNull = (line.Last() == '*');
                                    if (isNull == false && emptyColsMap[k] == true)
                                    {
                                        emptyColsMap[k] = false;
                                    }

                                    line = tr.ReadLine();
                                }
                            }
                        }
                        else
                        {
                            for (int k = 0; k < colCount; k++)
                            {
                                emptyColsMap[k] = false;
                            };
                        }

                        #endregion
                    }
                }

                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                using (FileStream fw = new FileStream(destination, FileMode.Create, FileAccess.Write))
                {                    
                    using (TextReader tr = new StreamReader(fs))
                    using (TextWriter tw = new StreamWriter(fw))
                    {
                        string line;
                        
                        line = tr.ReadLine(); // ler informação de data
                        line = tr.ReadLine(); // ler informação das colunas

                        #region ler os nomes das colunas

                        StringBuilder sbHeader = new StringBuilder();

                        sbHeader.Append("Data e Hora");
                        sbHeader.Append(separator);

                        for (int k = 0; k < colCount; k++)
                        {
                            line = tr.ReadLine();
                            if (line == null)
                                break;

                            if (removeEmptyCols && emptyColsMap[k])
                                continue;

                            sbHeader.Append(line.Trim()); // column name
                            sbHeader.Append(separator);
                        }

                        tw.WriteLine(sbHeader.ToString().TrimEnd(';'));

                        #endregion

                        #region ler amostras e escrever

                        rowCount = 0;

                        line = tr.ReadLine();

                        while (line != null)
                        {
                            // obter hora da amostra
                            string[] lparts = line.Split(new char[] { ' ' }, 2, StringSplitOptions.None);
                            if (lparts[0].StartsWith("h") == false)
                                throw new Exception("Erro ao obter hora da amostra");

                            DateTime time;
                            if (DateTime.TryParse(lparts[1], out time) == false)
                                throw new Exception("Erro ao obter hora da amostra");

                            StringBuilder sbLine = new StringBuilder();

                            sbLine.Append(dateStr + time.ToString("HH:mm:ss")); // date and time
                            sbLine.Append(separator);

                            for (int k = 0; k < colCount; k++)
                            {
                                line = tr.ReadLine();
                                if (line == null)
                                    break;

                                if (removeEmptyCols && emptyColsMap[k]) // ignore column
                                    continue;

                                // write value
                                if (line.Last() != '*')
                                {
                                    sbLine.Append(line.Trim());
                                }
                                sbLine.Append(separator);
                            }

                            if (line == null)
                                continue;

                            tw.WriteLine(sbLine.ToString().TrimEnd(';'));
                            rowCount++;

                            line = tr.ReadLine();
                        }

                        #endregion
                    }

                    #region verificar limites da tabela (Excel 2003)
                    
                    // O excel 2003 não é capaz de mostras mais de 65.536 linhas ou 256 colunas

                    int maxColumns;
                    if (removeEmptyCols)
                    {
                        maxColumns = 0;
                        foreach (bool isEmptyCol in emptyColsMap)
                        {
                            if (isEmptyCol == false)
                                maxColumns++;
                        }
                    }
                    else
                    {
                        maxColumns = colCount;
                    }

                    if (colCount > 256 && excel2003Flag == false)
                    {
                        excel2003Flag = true;
                    }
                    else if (rowCount > 65536 && excel2003Flag == false)
                    {
                        excel2003Flag = true;
                    }

                    #endregion
                }

                TimeSpan elapsed = DateTime.Now - start;

                System.Diagnostics.Debug.WriteLine("convert and export time: " + elapsed);

                return true;

            }
            catch (Exception e)
            {
                exception = e;
                return false;
            }
        }

        public static String RemoveDiacritics(String s)
        {
            String normalizedString = s.Normalize(NormalizationForm.FormD);
            StringBuilder stringBuilder = new StringBuilder();

            for (int i = 0; i < normalizedString.Length; i++)
            {
                Char c = normalizedString[i];
                if (System.Globalization.CharUnicodeInfo.GetUnicodeCategory(c) != System.Globalization.UnicodeCategory.NonSpacingMark)
                    stringBuilder.Append(c);
            }

            return stringBuilder.ToString();
        }

        private void convertBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (convertBackgroundWorker.CancellationPending)
            {
                progressBar1.Style = ProgressBarStyle.Marquee;
            }
            else if (e.ProgressPercentage >= 0)
            {
                progressBar1.Value = e.ProgressPercentage;
                progressBar1.Style = ProgressBarStyle.Continuous;
                Application.DoEvents();
            }
            else
            {
                progressBar1.Style = ProgressBarStyle.Marquee;
            }
        }

        private void convertBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            folderTextBox.Enabled = true;
            openButton.Enabled = true;
            checkedListBox1.Enabled = true;
            removeColsCheckBox.Enabled = true;

            bool canConvert = checkedListBox1.CheckedItems.Count > 0;

            convertButton.Enabled = canConvert;
            convertButton.Visible = true;
            cancelButton.Enabled = false;
            cancelButton.Visible = false;
            closeButton.Enabled = true;

            Application.DoEvents();

            progressBar1.Visible = false;

            if (e.Cancelled)
            {
                resultLabel.Visible = false;
                toolTip1.SetToolTip(resultLabel, null);
            }
            else if (e.Error != null)
            {
                resultLabel.Visible = true;
                resultLabel.Text = e.Error.Message;
                toolTip1.SetToolTip(resultLabel, resultLabel.Text);
            }
            else if (e.Result != null)
            {
                object[] results = (object[])e.Result;

                resultLabel.Visible = true;
                resultLabel.Text = (int)results[0] + " foram convertidos com sucesso";
                toolTip1.SetToolTip(resultLabel, resultLabel.Text);

                if (results.Length > 1 && (bool)results[1] == true)
                {
                    MessageBox.Show(this, "Foi indentificado pelo menos um arquivo atinge os limites do Microsft Excel 2003, que exibe no máximo 65.536 linhas e 256 colunas.", "Atenção", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
            }
            else
            {
                resultLabel.Visible = false;
                toolTip1.SetToolTip(resultLabel, null);
            }


            this.UseWaitCursor = false;
        }

        private void openButton_Click(object sender, EventArgs e)
        {
            if (folderBrowserDialog.ShowDialog(this) == DialogResult.OK)
            {
                folderTextBox.Text = folderBrowserDialog.SelectedPath;

                ListFiles();
            }
        }

        private void ListFiles()
        {
            checkedListBox1.BeginUpdate();
            checkedListBox1.Items.Clear();

            string folder = folderTextBox.Text;

            bool hasFiles = false;

            if (Directory.Exists(folder) == false)
            {
                checkedListBox1.Items.Add("A pasta é inválida ou não existe...");
                checkedListBox1.SetItemChecked(0, false);
            }
            else
            {
                // list available files

                IEnumerable<string> foundFiles = Directory.EnumerateFiles(folder, "*.pas", SearchOption.TopDirectoryOnly);

                if (foundFiles.Count() == 0)
                {
                    checkedListBox1.Items.Add("A pasta é inválida ou não existe...");
                    checkedListBox1.SetItemChecked(0, false);
                }
                else
                {
                    hasFiles = true;

                    foreach (string file in foundFiles)
                    {
                        int index = checkedListBox1.Items.Add(file);
                        checkedListBox1.SetItemChecked(index, true);
                    }
                }
            }

            if (hasFiles)
            {
                checkedListBox1.ForeColor = SystemColors.WindowText;
                convertButton.Visible = true;
                convertButton.Enabled = true;
                cancelButton.Visible = false;
                cancelButton.Enabled = false;
                selectAllCheckBox.Enabled = true;
                selectAllCheckBox.Checked = true;
                checkedListBox1.Enabled = true;
                removeColsCheckBox.Enabled = true;
            }
            else
            {
                checkedListBox1.ForeColor = Color.Gray;
                convertButton.Visible = true;
                convertButton.Enabled = false;
                cancelButton.Visible = false;
                cancelButton.Enabled = false;
                selectAllCheckBox.Enabled = false;
                selectAllCheckBox.Checked = false;
                checkedListBox1.Enabled = false;
                removeColsCheckBox.Enabled = false;
            }

            checkedListBox1.EndUpdate();
        }

        private void folderTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter)
            {
                e.Handled = true;
                ListFiles();
            }
        }

        private void convertButton_Click(object sender, EventArgs e)
        {
            if (convertBackgroundWorker.IsBusy)
                return;

            int count = checkedListBox1.CheckedItems.Count;

            if (count <= 0)
            {
                MessageBox.Show(this, "Por favor, selecione ao menos um arquivo a ser convertido.", "Atenção", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                return;
            }

            List<string> selectedFiles = new List<string>(count);
            foreach (string item in checkedListBox1.CheckedItems)
            {
                selectedFiles.Add(item);
            }

            folderTextBox.Enabled = false;
            openButton.Enabled = false;
            checkedListBox1.Enabled = false;
            removeColsCheckBox.Enabled = false;

            convertButton.Enabled = false;
            convertButton.Visible = false;
            cancelButton.Enabled = true;
            cancelButton.Visible = true;
            closeButton.Enabled = false;

            resultLabel.Visible = false;

            progressBar1.Style = ProgressBarStyle.Marquee;
            progressBar1.Visible = true;

            this.UseWaitCursor = true;

            convertBackgroundWorker.RunWorkerAsync(new object[] {
                selectedFiles.ToArray(),
                removeColsCheckBox.Checked 
            });
        }

        private void cancelButton_Click(object sender, EventArgs e)
        {
            if (convertBackgroundWorker.IsBusy)
            {
                convertBackgroundWorker.CancelAsync();
            }
            progressBar1.Style = ProgressBarStyle.Marquee;
            cancelButton.Enabled = false;
        }

        private bool suspendChecked = false;

        private void selectAllCheckBox_Click(object sender, EventArgs e)
        {
            bool allChecked = checkedListBox1.CheckedItems.Count == checkedListBox1.Items.Count;
            bool noneChecked = checkedListBox1.CheckedItems.Count == 0;

            bool newState;

            if (allChecked)
            {
                newState = false;
            }
            else if (noneChecked)
            {
                newState = true;
            }
            else
            {
                newState = true;
            }

            checkedListBox1.BeginUpdate();
            suspendChecked = true;

            for (int i = 0; i < checkedListBox1.Items.Count; i++)
            {
                checkedListBox1.SetItemChecked(i, newState);
            }

            suspendChecked = false;
            checkedListBox1.EndUpdate();

            checkedListBox1_ItemCheck(null, null);
        }

        private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
        {
            if (suspendChecked)
                return;

            int checkedCount = checkedListBox1.CheckedItems.Count;

            if (e != null)
            {
                if (checkedListBox1.CheckedIndices.Contains(e.Index))
                {
                    if (e.NewValue == CheckState.Unchecked)
                        checkedCount--;
                }
                else if (e.NewValue == CheckState.Checked)
                {
                    checkedCount++;
                }
            }

            bool allChecked = checkedCount == checkedListBox1.Items.Count;
            bool noneChecked = checkedListBox1.CheckedItems.Count == 0;

            selectAllCheckBox.CheckState = allChecked ? CheckState.Checked : (noneChecked ? CheckState.Unchecked : CheckState.Indeterminate);
        }

        private void closeButton_Click(object sender, EventArgs e)
        {
            this.Close();
            Application.Exit();
        }
    }
}
